<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <link rel="stylesheet" href="../index.css">
  <link rel="stylesheet" href="../common.css">
</head>
<body>
  <div class="container">
    <div class="page-content">
      <div class="content-box">
        <div class="content-title">享元模式</div>
        <div class="content-intr-txt">
          享元模式是一种用于性能优化的模式。使用共享技术来有效支持大量细粒度的对象。
        </div><!-- /.content-intr-txt -->
        <div class="example-txt">
          假设内衣分别有50中男士和女士内衣，为了推销产品，工厂决定生产一些塑料模特来穿上内衣拍成广告。在不使用享元模式的情况下，需要各生成50个男女模特。
        </div><!-- /.content-intr-txt -->
        <pre style="text-align: left;">
					// 不享元模式，给每套内衣都生成一个对象，如果内衣的种类比较多
					// 则生成对象的数量比较多，影响性能
					<code>
						var Model = function(sex, underwear) {
							this.sex = sex;
							this.underwear = underwear;
						}
						Model.prototype.takePhoto = function() {
							console.log('sex=' + this.sex + ' underwear=' + this.underwear);
						}

						for (var i = 0; i < 50; i++) {
							var maleModel = new Model('male', 'underwear' + i);
							maleModel.takePhoto();
						}

						for (var j = 0; j < 50; j++) {
							var femaleModel = new Model('male', 'underwear' + i);
							femaleModel.takePhoto();
						}
					</code>
				</pre>
        <pre style="text-align: left;">
					// 我们不需要生成50个男女模特，只需要男女各一个模特就行，然后给他穿上不同的内衣
					<code>
						var Model = function(sex, underwear) {
							this.sex = sex;
						}

						Model.prototype.takePhoto = function() {
							console.log('sex=' + this.sex + ' underwear=' + this.underwear);
						}

						var maleModel = new Model('male');
						var femaleModel = new Model('female');

						for (var i = 0; i < 50; i++) {
							maleModel.underwear = 'underwear'+i;
							maleModel.takePhoto();
						}

						for (var j = 0; j < 50; j++) {
							femaleModel.underwear = 'underwear'+i;
							femaleModel.takePhoto();
						}
					</code>
				</pre>
      </div>
      <div class="content-box">
        <div class="content-title">内部状态与外部状态</div>
        <div class="content-intr-txt">享元模式要求将对象的属性划分为内部状态与外部状态，享元模式的目标是精良减少共享对象的的数量，如何划分内部与外部状态。</div>
        <ul class="intr-list">
          <li class="list-item-intr">内部状态存储于对象内部</li>
          <li class="list-item-intr">内部对象可以被一些对象共享</li>
          <li class="list-item-intr">内部状态独立于具体的场景，通常不会改变</li>
          <li class="list-item-intr">外部状态取决于具体的场景，并根据场景而变化，外部状态不能被共享</li>
          <li class="list-item-intr">剥离了外部状态的对象成为共享对象，外部状态在必要时被传入共享对象来组装成一个完整的对象。</li>
        </ul>
      </div>

      <div class="content-box">
        <div class="content-title">享元模式的通用结构</div>
        <ul class="intr-list">
          <li class="list-item-intr">通过构造函数显示new出男女两个model对象，在其他系统中，也许并不是一开始就需要所有的共享对象</li>
          <li class="list-item-intr">给model对象手动设置underwear外部状态，在复杂的系统中，这不是一个最好的法师，因为外部状态可能会相当复杂，与共享对象的联系会变得困难。</li>
        </ul>
        <div class="content-intr-txt">我们通过一个对象工厂来解决第一个问题，只有当某种共享对象真正需要时，才从工厂中创建出来。使用管理器来记录对象相关得外部状态，使这些外部状态通过某个狗子和共享对象联系起来。</div>
        <div class="content-intr-txt">在不使用享元模式选择多文件上传时，当用户选择文件达到成千上百时，就会创建成千上百个的对象严重影响性能。如下边代码：会存在很多uploadObj对象</div>
        <pre>
          // 模拟用户多文件上传的
          <code>
            var id = 0;
            window.startUpload = function(uploadType, files) {
              for (var i = 0, file; file = files[i++];) {
                var uploadObj = new Upload(uploadType, file.fileName, file.fileSize);
                uploadObj.init(id++);
              }
            }

            var Upload = function(uploadType, fileName, fileSize) {
              this.uploadType = uploadType;
              this.fileName = fileName;
              this.fileSize = fileSize;
              this.dom = null;
            }

            Upload.prototype.init = function(id) {
              var that = this;
              this.id = id;
              this.dom = document.createElement('div');
              this.dom.innerHTML = `<span>文件名称：${this.fileName}，文件大小：${this.fileSize}</span><button class="delFile">删除</button>`;
              this.dom.querySelector('.delFile').onclick = function() {
                that.delFile();
              }

              document.querySelector('.upload-example-box').appendChild(this.dom);
            }
            Upload.prototype.delFile = function() {
              if (this.fileSize < 3000) {
                return this.dom.parentNode.removeChild(this.dom)
              }

              if (window.confirm('确定删除该文件吗？' + this.fileName)){
                return this.dom.parentNode.removeChild(this.dom)
              }
            }

            startUpload('plugin', [{
              fileName: '1.txt',
              fileSize: 1000
            },{
              fileName: '2.txt',
              fileSize: 2000
            },{
              fileName: '3.txt',
              fileSize: 3000
            }])

            startUpload('plugin', [{
              fileName: '4.txt',
              fileSize: 4000
            },{
              fileName: '5.txt',
              fileSize: 5000
            },{
              fileName: '6.txt',
              fileSize: 6000
            }])
          </code>
        </pre>
      </div>

      <div class="content-box">
        <div class="content-title">模拟文件上传效果：</div>
        <div class="upload-example-box"></div>
      </div>

      <div class="content-box">
        <div class="content-title">利用享元模式重构文件上传</div>
        <div class="content-intr-txt">享元模式区分内部状态与外部状态的重要几点：</div>
        <ul class="intr-list">
          <li class="list-item-intr">内部状态存在与对象的内部。</li>
          <li class="list-item-intr">内部状态可以被一些对象继承。</li>
          <li class="list-item-intr">内部状态独立于具体的场景，通常不会改变。</li>
          <li class="list-item-intr">外部状态取决于具体的场景，并根据场景的变化而变化，外部状态不能被继承。</li>
        </ul>
        <div class="content-title">重构效果：</div>
        <div class="upload-example-box-1"></div>
        <div class="content-intr-txt">在Upload对象中uploadType属性是必须的，且有两种可能plugin与Flash。可以利用工厂函数来解决共享对象在真正需要时才来创建。使用管理器来记录相关外部状态，通过某个钩子与共享对象联系起来。</div>
        <div class="content-intr-txt">代码如下：</div>
        <pre>
          // 使用享元模式来重构上传代码。
          <code>
            var uuid = 0;
            var reUpload = function(uplaodType) {
              this.uploadType = uplaodType
            }

            var uploadFactory = (function() {
              var createdFlyweight = {};
              return {
                create: function(uploadType) {
                  if (createdFlyweight[uploadType]) {
                    return createdFlyweight[uploadType]
                  }

                  return createdFlyweight[uploadType] = new reUpload(uploadType)
                }
              }
            })()

            reUpload.prototype.delFile = function(id) {
              uploadManager.setExternalState(id, this);

              if (this.fileSize < 3000) {
                return this.dom.parentNode.removeChild(this.dom)
              }

              if (window.confirm('确定删除该文件吗？' + this.fileName)) {
                return this.dom.parentNode.removeChild(this.dom)
              }
            }

            var uploadManager = (function(){
              var uploadDataBase = {};

              return {
                add: function(id, uploadType, fileName, fileSize) {
                  var flyweight = uploadFactory.create(uploadType);
                  var dom = document.createElement('div');
                  dom.innerHTML = `<span>文件名称：${fileName}，文件大小：${fileSize}</span><button class="delFile">删除</button>`;

                  dom.querySelector('.delFile').onclick = function() {
                    flyweight.delFile(id);
                  }

                  document.querySelector('.upload-example-box-1').appendChild(dom);

                  uploadDataBase[id] = {
                    fileName: fileName,
                    fileSize: fileSize,
                    dom: dom
                  }

                  return flyweight;
                },

                setExternalState: function(id, flyweight) {
                    var uploadData = uploadDataBase[id];

                    for (var i in uploadData) {
                      flyweight[i] = uploadData[i]
                    }
                }
              }
            })();

            window.reStartUpload = function(uploadType, files) {
              for (var i = 0, file; file = files[i++];) {
                var uploadObj = uploadManager.add(++uuid, uploadType, file.fileName, file.fileSize);
              }
            }

            reStartUpload('plugin', [{
              fileName: '1.txt',
              fileSize: 1000
            },{
              fileName: '2.txt',
              fileSize: 2000
            },{
              fileName: '3.txt',
              fileSize: 3000
            }]);
            reStartUpload('Flash', [{
              fileName: '4.txt',
              fileSize: 4000
            },{
              fileName: '5.txt',
              fileSize: 5000
            },{
              fileName: '6.txt',
              fileSize: 6000
            }]);
          </code>
        </pre>
      </div>

      <div class="content-box">
        <div class="content-title">享元模式的通用结构</div>
        <ul class="intr-list">
          <li class="list-item-intr">通过构造函数显示new出男女两个model对象，在其他系统中，也许并不是一开始就需要所有的共享对象</li>
          <li class="list-item-intr">给model对象手动设置underwear外部状态，在复杂的系统中，这不是一个最好的法师，因为外部状态可能会相当复杂，与共享对象的联系会变得困难。</li>
        </ul>
        <div class="content-intr-txt">我们通过一个对象工厂来解决第一个问题，只有当某种共享对象真正需要时，才从工厂中创建出来。使用管理器来记录对象相关得外部状态，使这些外部状态通过某个狗子和共享对象联系起来。</div>
        <div class="re-example-box"></div>
      </div>

      <div class="content-box">
        <div class="content-title">享元模式的适用性</div>
        <div class="content-intr-txt">
          享元模式是一种很好的性能优化方案，但它会带来一些复杂性的问题，如需要多维护一个factory与manager对象。所以享元模式的使用情况有：
        </div>
        <ul class="intr-list">
          <ul class="list-item-intr">一个程序中使用了大量相似的对象</ul>
          <ul class="list-item-intr">由于使用了大量对象，造成很大的内存开销</ul>
          <ul class="list-item-intr">对象的大多数状态都可以变为外部状态</ul>
          <ul class="list-item-intr">剥离出对象的外部状态之后，可以用相对较少的共享对象取代大量对象</ul>
        </ul>
        <div class="content-title">对象池</div>
        <div class="content-intr-txt">对象池是一个装在空闲对象的对象，当需要对象是首先在对象池中获取，如果对象池为空，则创建一个对象，在该对象履行完职责后放回对象池。</div>
        <div class="content-intr-txt">如：在页面上按照大写字母的顺序创建n个标记</div>
        <span class="span-click-btn clear-flag-btn1">清楚标记</span>
        <span class="span-click-btn create-6-flags">重新创建6个标记</span>
        <style>
          .pointer-icon-1 {
            float: left;
            width: 16px;
            height: 16px;
            line-height: 14px;
            margin-right: 5px;
            font-size: 12px;
            text-align: center;
            background-color: rgba(255,0,0,.6);
            border-radius: 50%;
            color: #fff;
          }
        </style>
        <div class="object-pool-box-1" style="overflow: hidden;"></div>
        <div class="list-item-intr">代码如下：</div>
        <pre>
					// 利用对象池按照大写字母顺序随机创建标记
					// 工厂函数创建对象
					<code>
						var createFlagFactory = (function() {
							var flagPool = [];

							return {
								create: function() {
									if (flagPool.length) {
										return flagPool.shift();
									} else {
										var span = document.createElement('span');
										span.className = 'pointer-icon-1';
										return span;
									}
								},
								recover: function(flag) {
									flagPool.unshift(flag);
									console.log(flagPool)
								}
							}
						})();

						var tempFlagAry = [];

						for (var i = 0, str; str = ['A', 'B'][i++];) {
							var flag = createFlagFactory.create();
							flag.innerHTML = str;
							tempFlagAry.push(flag);
							document.querySelector('.object-pool-box-1').appendChild(flag);
						}

						document.querySelector('.clear-flag-btn1').onclick = function() {
							var l = tempFlagAry.length - 1
							for (var i = l, flag; i >= 0; i--) {
								flag = tempFlagAry.splice(i, 1)[0];
								createFlagFactory.recover(flag);
								flag.parentNode.removeChild(flag);
							}
						}

						document.querySelector('.create-6-flags').onclick = function() {
							for (var i = 0, str; str = ['A', 'B', 'C', 'D', 'E', 'F'][i++];) {
								var flag = createFlagFactory.create();
								flag.innerHTML = str;
								tempFlagAry.push(flag);
								document.querySelector('.object-pool-box-1').appendChild(flag);
							}
						}
					</code>
				</pre>
      </div>

      <div class="content-box">
        <div class="content-title">对象池的通用写法</div>
      </div>

    </div>
  </div>
  <script>
    (function(){
      // 不享元模式，给每套内衣都生成一个对象，如果内衣的种类比较多
      // 则生成对象的数量比较多，影响性能
      /*var Model = function(sex, underwear) {
        this.sex = sex;
        this.underwear = underwear;
      }
      Model.prototype.takePhoto = function() {
        console.log('sex=' + this.sex + ' underwear=' + this.underwear);
      }

      for (var i = 0; i < 50; i++) {
        var maleModel = new Model('male', 'underwear' + i);
        maleModel.takePhoto();
      }

      for (var j = 0; j < 50; j++) {
        var femaleModel = new Model('male', 'underwear' + i);
        femaleModel.takePhoto();
      }*/

      // 我们不需要生成50个男女模特，只需要男女各一个模特就行，然后给他穿上不同的内衣
      /*var Model = function(sex, underwear) {
        this.sex = sex;
      }

      Model.prototype.takePhoto = function() {
        console.log('sex=' + this.sex + ' underwear=' + this.underwear);
      }

      var maleModel = new Model('male');
      var femaleModel = new Model('female');

      for (var i = 0; i < 50; i++) {
        maleModel.underwear = 'underwear'+i;
        maleModel.takePhoto();
      }

      for (var j = 0; j < 50; j++) {
        femaleModel.underwear = 'underwear'+i;
        femaleModel.takePhoto();
      }*/

      // 模拟用户多文件上传的
      /*var id = 0;
      window.startUpload = function(uploadType, files) {
        for (var i = 0, file; file = files[i++];) {
          var uploadObj = new Upload(uploadType, file.fileName, file.fileSize);
          uploadObj.init(id++);
        }
      }
      var Upload = function(uploadType, fileName, fileSize) {
        this.uploadType = uploadType;
        this.fileName = fileName;
        this.fileSize = fileSize;
        this.dom = null;
      }

      Upload.prototype.init = function(id) {
        var that = this;
        this.id = id;
        this.dom = document.createElement('div');
        this.dom.innerHTML = `<span>文件名称：${this.fileName}，文件大小：${this.fileSize}</span><button class="delFile">删除</button>`;
        this.dom.querySelector('.delFile').onclick = function() {
          that.delFile();
        }

        document.querySelector('.upload-example-box').appendChild(this.dom);
      }
      Upload.prototype.delFile = function() {
        if (this.fileSize < 3000) {
          return this.dom.parentNode.removeChild(this.dom)
        }

        if (window.confirm('确定删除该文件吗？' + this.fileName)){
          return this.dom.parentNode.removeChild(this.dom)
        }
      }
      startUpload('plugin', [{
        fileName: '1.txt',
        fileSize: 1000
      },{
        fileName: '2.txt',
        fileSize: 2000
      },{
        fileName: '3.txt',
        fileSize: 3000
      }]);
      startUpload('Flash', [{
        fileName: '4.txt',
        fileSize: 4000
      },{
        fileName: '5.txt',
        fileSize: 5000
      },{
        fileName: '6.txt',
        fileSize: 6000
      }]);*/

      // 使用享元模式来重构上传代码。
      /*var uuid = 0;
      var reUpload = function(uplaodType) {
        this.uploadType = uplaodType
      }

      var uploadFactory = (function() {
        var createdFlyweight = {};
        return {
          create: function(uploadType) {
            if (createdFlyweight[uploadType]) {
              return createdFlyweight[uploadType]
            }

            return createdFlyweight[uploadType] = new reUpload(uploadType)
          }
        }
      })()

      reUpload.prototype.delFile = function(id) {
        uploadManager.setExternalState(id, this);

        if (this.fileSize < 3000) {
          return this.dom.parentNode.removeChild(this.dom)
        }

        if (window.confirm('确定删除该文件吗？' + this.fileName)) {
          return this.dom.parentNode.removeChild(this.dom)
        }
      }

      var uploadManager = (function(){
        var uploadDataBase = {};

        return {
          add: function(id, uploadType, fileName, fileSize) {
            var flyweight = uploadFactory.create(uploadType);
            var dom = document.createElement('div');
            dom.innerHTML = `<span>文件名称：${fileName}，文件大小：${fileSize}</span><button class="delFile">删除</button>`;

            dom.querySelector('.delFile').onclick = function() {
              flyweight.delFile(id);
            }

            document.querySelector('.upload-example-box-1').appendChild(dom);

            uploadDataBase[id] = {
              fileName: fileName,
              fileSize: fileSize,
              dom: dom
            }

            return flyweight;
          },

          setExternalState: function(id, flyweight) {
              var uploadData = uploadDataBase[id];

              for (var i in uploadData) {
                flyweight[i] = uploadData[i]
              }
          }
        }
      })();

      window.reStartUpload = function(uploadType, files) {
        for (var i = 0, file; file = files[i++];) {
          var uploadObj = uploadManager.add(++uuid, uploadType, file.fileName, file.fileSize);
        }
      }

      reStartUpload('plugin', [{
        fileName: '1.txt',
        fileSize: 1000
      },{
        fileName: '2.txt',
        fileSize: 2000
      },{
        fileName: '3.txt',
        fileSize: 3000
      }]);
      reStartUpload('Flash', [{
        fileName: '4.txt',
        fileSize: 4000
      },{
        fileName: '5.txt',
        fileSize: 5000
      },{
        fileName: '6.txt',
        fileSize: 6000
      }]);*/

      // 利用对象池按照大写字母顺序随机创建标记
      // 工厂函数创建对象
      /*var createFlagFactory = (function() {
        var flagPool = [];

        return {
          create: function() {
            if (flagPool.length) {
              return flagPool.shift();
            } else {
              var span = document.createElement('span');
              span.className = 'pointer-icon-1';
              return span;
            }
          },
          recover: function(flag) {
            flagPool.unshift(flag);
            console.log(flagPool)
          }
        }
      })();

      var tempFlagAry = [];

      for (var i = 0, str; str = ['A', 'B'][i++];) {
        var flag = createFlagFactory.create();
        flag.innerHTML = str;
        tempFlagAry.push(flag);
        document.querySelector('.object-pool-box-1').appendChild(flag);
      }

      document.querySelector('.clear-flag-btn1').onclick = function() {
        var l = tempFlagAry.length - 1
        for (var i = l, flag; i >= 0; i--) {
          flag = tempFlagAry.splice(i, 1)[0];
          createFlagFactory.recover(flag);
          flag.parentNode.removeChild(flag);
        }
      }

      document.querySelector('.create-6-flags').onclick = function() {
        for (var i = 0, str; str = ['A', 'B', 'C', 'D', 'E', 'F'][i++];) {
          var flag = createFlagFactory.create();
          flag.innerHTML = str;
          tempFlagAry.push(flag);
          document.querySelector('.object-pool-box-1').appendChild(flag);
        }
      }*/

      // 通用对象池的写法
      var classId = 0;
      var objectPoolFactory = function(objectFactory) {
        var objectPools = [];
        return {
          create: function() {
            var obj = objectPools.length > 0
                ? objectPools.shift()
                : objectFactory.apply(this, arguments);

            return obj;
          },

          recover: function(obj) {
            objectPools.push(obj);
          }
        }
      }

      var iframeFactory = objectPoolFactory(function() {
        var iframe = document.createElement('iframe');
        document.body.appendChild(iframe);
        iframe.className = 'iframe-'+(classId++)
        iframe.onload = function() {
          iframe.onload = null;
          iframeFactory.recover(iframe);
        }

        return iframe;
      });

      var iframe1 = iframeFactory.create();
      iframe1.src = 'http://www.baidu.com';
      var iframe2 = iframeFactory.create();
      iframe2.src = 'https://www.w3school.com.cn';
      setTimeout(function() {
        var iframe3 = iframeFactory.create();
        iframe3.src = 'http:// 163.com';
      }, 3200);
    })()
  </script>
</body>
</html>






































